package karma.pool.pool;

import karma.pool.pool.proxy.ProxyConnection;
import karma.pool.pool.proxy.ProxyFactory;
import karma.pool.pool.proxy.ProxyLeakReportRunnable;
import karma.pool.util.IBagEntry;
import karma.pool.util.LastElementList;
import karma.pool.util.clock.ClockFactory;
import lombok.extern.slf4j.Slf4j;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * Entry used in the Bag to track Connection instances.
 */
@Slf4j
public final class PoolEntry implements IBagEntry {

   private static final AtomicIntegerFieldUpdater<PoolEntry> stateUpdater;

   static {
      stateUpdater = AtomicIntegerFieldUpdater.newUpdater(PoolEntry.class, "state");
   }

   private final LastElementList<Statement> openStatements;
   private final Pool pool;
   private final boolean isReadOnly;
   private final boolean isAutoCommit;
   public Connection connection;
   long lastAccessed;
   long lastBorrowed;
   @SuppressWarnings("FieldCanBeLocal")
   private volatile int state = 0;
   private volatile boolean evict;
   private volatile ScheduledFuture<?> endOfLife;

   PoolEntry(final Connection connection, final AbstractPool pool, final boolean isReadOnly, final boolean isAutoCommit) {
      this.connection = connection;
      this.pool = (Pool) pool;
      this.isReadOnly = isReadOnly;
      this.isAutoCommit = isAutoCommit;
      this.lastAccessed = ClockFactory.currentTime();
      this.openStatements = new LastElementList<>(Statement.class, 16);
   }

   /**
    * Release this entry back to the pool.
    *
    * @param lastAccessed last access time-stamp
    */
   public void recycle(final long lastAccessed) {
      if (connection != null) {
         this.lastAccessed = lastAccessed;
         pool.recycle(this);
      }
   }

   /**
    * Set the end of life {@link ScheduledFuture}.
    *
    * @param endOfLife this PoolEntry/Connection's end of life {@link ScheduledFuture}
    */
   void setFutureEol(final ScheduledFuture<?> endOfLife) {
      this.endOfLife = endOfLife;
   }

   Connection createProxyConnection(final ProxyLeakReportRunnable leakTask, final long now) {
      return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
   }

   public void resetConnectionState(final ProxyConnection proxyConnection, final int dirtyStatus) throws SQLException {
      pool.resetConnectionState(connection, proxyConnection, dirtyStatus);
   }

   public String getPoolName() {
      return pool.toString();
   }

   public boolean isMarkedEvicted() {
      return evict;
   }

   void markEvicted() {
      this.evict = true;
   }

   public void evict(final String closureReason) {
      pool.closeConnection(this, closureReason);
   }

   /**
    * Returns millis since lastBorrowed
    */
   long getMillisSinceBorrowed() {
      return ClockFactory.elapsedMillis(lastBorrowed);
   }


   @Override
   public String toString() {
      final long now = ClockFactory.currentTime();
      return connection
         + ", accessed " + ClockFactory.elapsedDisplayString(lastAccessed, now) + " ago, "
         + stateToString();
   }


   @Override
   public int getState() {
      return stateUpdater.get(this);
   }


   @Override
   public void setState(int update) {
      stateUpdater.set(this, update);
   }


   @Override
   public boolean compareAndSet(int expect, int update) {
      return stateUpdater.compareAndSet(this, expect, update);
   }

   Connection close() {
      ScheduledFuture<?> eol = endOfLife;
      if (eol != null && !eol.isDone() && !eol.cancel(false)) {
         log.warn("{} - maxLifeTime expiration task cancellation unexpectedly returned false for proxyConnection {}", getPoolName(), connection);
      }

      Connection connection = this.connection;
      this.connection = null;
      endOfLife = null;
      return connection;
   }

   private String stateToString() {
      switch (state) {
         case state_using:
            return "in_use";
         case state_not_used:
            return "not_in_use";
         case state_removed:
            return "removed";
         case state_reserved:
            return "reserved";
         default:
            return "invalid";
      }
   }
}
